package ec.fugu.amf;

import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;

public class Amf3Serializer extends AmfSerializer {

	public Amf3Serializer(DataOutputStream stream) {
		super(stream);
	}

	public boolean writeAMFData(Object data, Byte forcetype) throws IOException {
		boolean flagType = false;
		boolean returnValue = false; 
		byte type = Amf0Constants.DT_UNDEFINED;

		if(forcetype == null){
			if(data == null){
				type = Amf3Constants.DT_NULL;
				flagType = true;
			}
			if(!flagType && data instanceof Boolean){
				type = ((Boolean)data).booleanValue()?Amf3Constants.DT_BOOL_TRUE:Amf3Constants.DT_BOOL_FALSE;
				flagType = true;
			}
			if(!flagType && data instanceof Float || data instanceof Double){
				type = Amf3Constants.DT_NUMBER;
				flagType = true;
			}
			if(!flagType && data instanceof Integer){
				type = Amf3Constants.DT_INTEGER;
				flagType = true;
			}
			if(!flagType && data instanceof String){
				type = Amf3Constants.DT_STRING;
				flagType = true;
			}
			if(!flagType && data.getClass().isArray()){
				type = Amf3Constants.DT_ARRAY;
				flagType = true;
			}
			if(!flagType && data instanceof Hashtable){
				type = Amf3Constants.DT_MIXEDARRAY;
				flagType = true;
			}
			if(!flagType){
				if(data instanceof AmfByteArray)
					type = Amf3Constants.DT_BYTEARRAY;
				else if(data instanceof Date)
					type = Amf3Constants.DT_DATE;
				else 
					type = Amf3Constants.DT_OBJECT;
			}
		} else {
			type = forcetype.byteValue();
		}
		this.outputStream.write(type);
		switch (type) {
			case Amf3Constants.DT_NULL: break; 
			case Amf3Constants.DT_BOOL_FALSE: break;
			case Amf3Constants.DT_BOOL_TRUE: break;
			case Amf3Constants.DT_INTEGER: returnValue = this.writeInt(((Integer)data).intValue());
										break;
			case Amf3Constants.DT_NUMBER: this.outputStream.writeDouble(Double.parseDouble(data.toString()));
										returnValue = true;
										break;
			case Amf3Constants.DT_STRING: returnValue = this.writeString(data.toString());
										break;
			case Amf3Constants.DT_DATE: returnValue = this.writeDate((Date)data);
										break;
			case Amf3Constants.DT_ARRAY: returnValue = this.writeArray(data);
										break;
			case Amf3Constants.DT_MIXEDARRAY: returnValue = this.writeHashTable(data);
										break;
			case Amf3Constants.DT_OBJECT: returnValue = this.writeObject(data);
										break;
			case Amf3Constants.DT_BYTEARRAY: returnValue = this.writeByteArray((AmfByteArray)data);
										break;
		}
		return returnValue;
	}

	public boolean writeInt(int data) throws IOException{
		boolean returnValue = true;
		if ( ( data & 0xffffff80 ) == 0 ){
			this.outputStream.write(data);
			return returnValue; 
		}
		if ( ( data & 0xffffc000 ) == 0 ){
			this.outputStream.write( ( data >> 7 ) | 0x80 );
			this.outputStream.write( data & 0x7f );
			return returnValue;
		}
		if ( ( data & 0xffe00000 ) == 0 ){
			this.outputStream.write( ( data >> 14 ) | 0x80 );
			this.outputStream.write( ( data >> 7 ) | 0x80 );
			this.outputStream.write( data & 0x7f );
			return returnValue;
		}
		this.outputStream.write( ( data >> 22 ) | 0x80 );
		this.outputStream.write( ( data >> 15 ) | 0x80 );
		this.outputStream.write( ( data >> 8 ) | 0x80 );
		this.outputStream.write( data & 0xff );
		return returnValue;
	}
	
	public boolean writeString (String data) throws IOException{
		boolean returnValue = true;
		int strref = data.length() << 1 | 0x01;
        this.writeInt(strref);
        this.outputStream.writeUTF(data);
		return returnValue;
	}
	
	public boolean writeDate (Date data) throws IOException{
		boolean returnValue = true;
		this.writeInt(0x01);
		this.outputStream.writeDouble(Double.parseDouble(new Long(((Date)data).getTime()*1000).toString()));
		return returnValue;
	}

	public boolean writeArray(Object data) throws IOException{
		boolean returnValue = true;
		Object[] values = (Object[])data;
        int arrId = (values.length << 1) | 0x01;
		this.writeInt(arrId);
		for(int i=0; i<values.length; i++){
			this.writeAMFData(values[i], null);
		}
		return returnValue;
	}
	
	public boolean writeHashTable(Object data) throws IOException {
		boolean returnValue = true;
		Hashtable ht = (Hashtable)data;
		Enumeration e = ht.keys();
		while(e.hasMoreElements()){
			String key = (String)e.nextElement();
			this.writeString(key);
			this.writeAMFData(ht.get(key), null);
		}
		this.writeString("");
		return returnValue;
	}

	
	public boolean writeByteArray(AmfByteArray data) throws IOException{
		boolean returnValue = true;
		this.writeString(data.getData());
		return returnValue;
	}

	//TODO: unimplemented
	public boolean writeObject(Object data){
		boolean returnValue = false;
		
		return returnValue;
	}

}
